/*

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
/*
 * Created on Nov 23, 2008
 */

package com.bigdata.config;

import java.io.IOException;
import java.util.Properties;
import java.util.UUID;

import org.apache.log4j.Logger;

import com.bigdata.btree.BTree;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.journal.IIndexManager;
import com.bigdata.relation.RelationSchema;
import com.bigdata.service.DataService;
import com.bigdata.service.IBigdataFederation;
import com.bigdata.service.IDataService;
import com.bigdata.util.NV;

/**
 * Base class for managing the initial configuration metadata for indices and
 * locatable resources.
 * 
 * @todo There are some drawbacks with this approach. It remains to be seen
 *       whether this can be improved on readily.
 *       <p>
 *       We can not report properties that DO NOT correspond to any known
 *       property within the umbrella bigdata namespace (as log4j does) because
 *       we do not make a closed world assumption for properties in that
 *       namespace.
 *       <p>
 *       We can not interpret properties given in a Java code style (as jini
 *       does with its Configuration object).
 *       <p>
 *       We are not using the Java beans model so you can not describe property
 *       values or instantiate objects using reflection. Instead, the logic for
 *       that stuff shows up in the code for the class that is being configured.
 *       <p>
 *       This presumes a fixed syntactic relation between a resource/index and
 *       its container rather than the explicit relation defined by
 *       {@link RelationSchema#CONTAINER}.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
public class Configuration {

    /**
     * Property values are logged at INFO.
     */
    protected static final transient Logger log = Logger.getLogger(Configuration.class);
    
    /**
     * The prefix for namespace specific property value overrides.
     */
    public static final transient String NAMESPACE = "com.bigdata.namespace";

    /**
     * The namespace separator character.
     */
    public static final transient char DOT = '.';
    
    /**
     * Return the value for property, which may be the default value, a global
     * override, or a namespace override. Defaults are assigned by three
     * mechanisms.
     * <ol>
     * 
     * <li>Default values are generally described in the javadoc for
     * <code>Options</code> interfaces. The specific default is supplied by
     * the caller and will be used if the value is not overridden using any of
     * the other methods.</li>
     * 
     * <li>The default value may be globally overridden using the property name.
     * For example, you can override the default branching factor for all
     * {@link BTree}s by specifying a value for
     * {@link IndexMetadata.Options#BTREE_BRANCHING_FACTOR}. In general, the
     * name of the property is declared by an interface along with its default
     * value. </li>
     * 
     * <li>Any value may be overridden by a value that is specific to the
     * <i>namespace</i> (or to any prefix of that <i>namespace</i> which can
     * be formed by chopping off the namespace at a {@link #DOT}). For example,
     * you can override the branching factor property for an index named
     * <code>foo.myIndex</code> by specifying a value for the property name
     * <code>com.bigdata.namespace.foo.myIndex.com.bigdata.btree.BTree.branchingFactor</code> ({@value #NAMESPACE}
     * is the {@link #NAMESPACE} prefix for overrides, <code>foo.myIndex</code>
     * is the name of the index, and
     * {@value IndexMetadata.Options#BTREE_BRANCHING_FACTOR} is the name of the
     * property that will be overridden for that index). Alternatively you can
     * override the branching factor for all indices in the "foo" relation by
     * specifying a value for the property name
     * <code>com.bigdata.namespace.foo.com.bigdata.btree.BTree.branchingFactor</code>.
     * Note: You can use {@link #getOverrideProperty(String, String)} to form
     * these property names automatically, including from within a Jini
     * configuration file.</li>
     * 
     * </ol>
     * 
     * @param indexManagerIsIgnored
     *            The value specified to the ctor (optional).
     * @param properties
     *            The properties object against which the value of the property
     *            will be resolved.
     * @param namespace
     *            The namespace of the index, relation, etc (optional).
     * @param propertyName
     *            The bare name of the property whose default value is requested
     *            (without the namespace).
     * @param defaultValue
     *            The value for that property that will be returned if the
     *            default has not been overridden as described above (optional).
     * 
     * @return The resolved value for the property.
     * 
     * @todo test when namespace is empty (journal uses that) and possibly null.
     */
    public static String getProperty(final IIndexManager indexManagerIsIgnored,
            final Properties properties, final String namespace,
            final String propertyName, final String defaultValue) {
    
        final NV nv = getProperty2(indexManagerIsIgnored, properties, namespace,
                propertyName, defaultValue);
        
        if(nv == null) return null;
        
        return nv.getValue();
        
    }
    
    /**
     * Variant returns both the name under which the value was discovered and
     * the value.
     * 
     * @param indexManagerIsIgnored
     * @param properties
     * @param namespace
     * @param globalName
     * @param defaultValue
     * @return
     */
    public static NV getProperty2(final IIndexManager indexManagerIsIgnored,
            final Properties properties, final String namespace,
            final String globalName, final String defaultValue) {

        // indexManager MAY be null.
        if (properties == null)
            throw new IllegalArgumentException();
//        if (namespace == null)
//            throw new IllegalArgumentException();
        if (globalName == null)
            throw new IllegalArgumentException();
        // defaultValue MAY be null.
        
        String key = null;
        String val = null;

        final String localName = globalName;//getLocalName(globalName);

        /*
         * Look for a namespace match, or a match on any prefix of the namespace
         * which can be formed by chopping off the last remaining component in
         * the namespace.
         */
        if (namespace != null) {
            
            // right size the buffer.
            final StringBuilder sb = new StringBuilder(NAMESPACE.length() + 1
                    + namespace.length() + 1 + localName.length());

            /*
             * Check the full namespace on the first pass then chop off the last
             * remaining component of each successive pass.
             */
            String prefix = namespace;
            
            while (prefix.length() > 0) {

                sb.setLength(0); // reset each time.
                sb.append(NAMESPACE);
                sb.append(DOT);
                sb.append(prefix);
                sb.append(DOT);
                sb.append(localName);

                // namespace override.
                val = properties.getProperty(key = sb.toString());

                if (val != null) {

                    // Match - will be logged below.
                    break;
                    
                }
                
                if (log.isDebugEnabled())
                    log.debug("No match: " + key);

                final int lastIndexOf = prefix.lastIndexOf(DOT);
                
                if (lastIndexOf == -1) {

                    // No match.
                    break;
                    
                }
                
                // chop off the last component and try again.
                prefix = prefix.substring(0, lastIndexOf);

            }
            
        }
        
        if (val == null) {

            // global override.
            val = properties.getProperty(key = globalName);
            
            if( val == null) {
            
                // no override.
                val = defaultValue;
                
            }

        }

        if (log.isInfoEnabled())
            log.info(key + "=" + val);
        
        return new NV(key, val);

    }

    /**
     * Variant converts to the specified generic type and validates the value.
     * 
     * @param <E>
     * @param indexManager
     * @param properties
     * @param namespace
     * @param globalName
     * @param defaultValue
     * @param validator
     * 
     * @return The validated value -or- <code>null</code> if there was no
     *         default.
     */
    public static <E> E getProperty(final IIndexManager indexManager,
            final Properties properties, final String namespace,
            final String globalName, final String defaultValue,
            final IValidator<E> validator)
            throws ConfigurationException {
    
        if (validator == null)
            throw new IllegalArgumentException();
        
        final NV nv = getProperty2(indexManager, properties, namespace,
                globalName, defaultValue);

        if (nv == null)
            return null;
        
        final E e = validator.parse(nv.getName(), nv.getValue());

        validator.accept(nv.getName(), nv.getValue(), e);
        
        return e;
        
    }
    
//    /**
//     * Return the last component of the globalName.
//     * <p>
//     * Note: If '.' does not appear, then lastIndexOf == -1 and beginIndex :=
//     * lastIndexOf + 1 == 0, so the localName will be the same as the
//     * globalName.
//     * 
//     * @param globalName
//     *            The global name of some property.
//     */
//    static protected String getLocalName(String globalName) {
//
//        final int lastIndexOf = globalName.lastIndexOf(DOT);
//
//        final String localName = globalName.substring(lastIndexOf + 1);
//
//        return localName;
//            
//    }
    
    /**
     * Resolve the value to a {@link DataService} {@link UUID}.
     * 
     * @param indexManager
     *            The index manager (optional).
     * @param val
     *            The value is either a {@link UUID} or a service name.
     * 
     * @return The {@link UUID} of the identified service -or- <code>null</code>
     *         if no service is identified for that value or if the
     *         <i>indexManager</i> is either not given or not an
     *         {@link IBigdataFederation}.
     * 
     * @throws IllegalArgumentException
     *             if the <i>val</i> is <code>null</code>.
     */
    static protected UUID resolveDataService(final IIndexManager indexManager,
            final String val) {

        if (indexManager == null)
            return null;

        if (val == null)
            throw new IllegalArgumentException();

        if (!(indexManager instanceof IBigdataFederation))
            return null;
        
        final IBigdataFederation fed = ((IBigdataFederation) indexManager);

        /*
         * Value is a UUID?
         */
        try {

            // valid UUID?
            return UUID.fromString(val);

        } catch (IllegalArgumentException ex) {

            // Ignore.

        }

        /*
         * Value is the name of a data service?
         */
        {
         
            final IDataService dataService = fed.getDataServiceByName(val);

            if (dataService != null) {

                try {

                    return dataService.getServiceUUID();
                    
                } catch (IOException ex) {
                    
                    throw new RuntimeException(ex);
                    
                }
                
            }

            // fall through.
            
        }
        
        // can't interpret the value.
        
        log.warn("Could not resolve: "+val);
        
        return null;

    }

    /**
     * Return the name that can be used to override the specified property for
     * the given namespace.
     * 
     * @param namespace
     *            The namespace (of an index, relation, etc).
     * @param property
     *            The global property name.
     *            
     * @return The name that is used to override that property for that
     *         namespace.
     */
    public static String getOverrideProperty(final String namespace,
            final String property) {
        
        final String override = NAMESPACE + DOT + namespace + DOT + property;
        
        if(log.isInfoEnabled()) {
            
            log.info("namespace=" + namespace + ", property=" + property
                    + ", override=" + override);

        }

        return override;

    }

}
